use crate::model::{CallArg, Prop};
use crate::Result;
use alloc::boxed::Box;
use alloc::collections::LinkedList;
use alloc::format;
use alloc::string::String;

use crate::defines::{PROTO_FLAGS_COMPRESS_MASK, PROTO_FLAGS_ENCRYPT_MASK};
use crate::errors::map_rmpv_read_err;
use crate::param::PersisParams;
use crate::util::from_slice_with_null_to_str;
use alloc::vec::Vec;
use embed_std::errors::Error;
use embed_std::sync::arc::Arc;
use embed_std::sync::channel::SyncSender;
use embed_std::sync::mutex::Mutex;
use mqtt::{MqttHandler, MqttMessage};
use rmpv::Value;

pub trait Handler: 'static + Send + Sync {
    fn on_set_props(&mut self, props: Vec<Prop>) -> Result<Vec<Prop>>;
    fn on_call_method(&mut self, method_name: &str, args: Vec<CallArg>) -> Result<Vec<Prop>>;
}

pub enum Message {
    Request(RemoteRequest),
    Response(u16, Response),
}

pub struct PendingRequest {
    pub seq: u16,
    pub created_time: u32,
    pub resp: Option<Response>,
}

pub struct RemoteRequest {
    pub topic: String,
    pub payload: Vec<u8>,
}

pub struct MessageHandler {
    sender: SyncSender<Message>,
}

impl MqttHandler for MessageHandler {
    fn handle(&self, topic: &str, msg: &MqttMessage) {
        debugln!(
            "mqtt message: topic {} qos {} id {} dup {} retained {}",
            topic,
            msg.qos,
            msg.msg_id,
            msg.dup,
            msg.retained,
        );
        if msg.dup != 0 {
            errorln!("skip dup message");
            return;
        }
        if msg.retained != 0 {
            errorln!("skip retained message");
            return;
        }
        if msg.payload.len() < 1 {
            errorln!("payload {} too small", msg.payload.len());
            return;
        }
        let items: Vec<&str> = topic.split("/").collect();
        if items.len() < 4 {
            errorln!("topic {} invalid", topic);
            return;
        }
        let flags = msg.payload[0];
        if flags & PROTO_FLAGS_ENCRYPT_MASK != 0 {
            errorln!("flags 0x{:x} encrypt, not support", flags);
            return;
        }
        if flags & PROTO_FLAGS_COMPRESS_MASK != 0 {
            errorln!("flags 0x{:x} compress, not support", flags);
            return;
        }
        let payload = &msg.payload[1..];
        debugln!("topic {} type {}", topic, items[items.len() - 2]);
        match items[items.len() - 2] {
            "r" => {
                let _ = self.handle_request(topic, items, payload).map_err(|e| {
                    errorln!("handle request {} failed: {:?}", topic, e);
                });
            }
            "a" => {
                let _ = self.handle_response(topic, items, payload).map_err(|e| {
                    errorln!("handle request {} failed: {:?}", topic, e);
                });
            }
            "b" => {
                errorln!("topic {} is broadcast, not support by device", topic);
            }
            _ => {
                errorln!("unknown topic {}", topic);
            }
        }
    }
}

impl MessageHandler {
    pub fn new(sender: SyncSender<Message>) -> Self {
        Self { sender }
    }

    fn handle_request(&self, topic: &str, top_items: Vec<&str>, payload: &[u8]) -> Result<()> {
        debugln!("handle request");
        self.check_if_match_my_info(topic, &top_items)?;
        self.sender.send(Message::Request(RemoteRequest {
            topic: String::from(topic),
            payload: Vec::from(payload),
        }))
    }

    fn handle_response(&self, topic: &str, top_items: Vec<&str>, payload: &[u8]) -> Result<()> {
        self.check_if_match_my_info(topic, &top_items)?;
        let seq: u16 = top_items[top_items.len() - 1]
            .parse()
            .map_err(|_| Error::Other(format!("{} invalid seq", top_items[top_items.len() - 1])))?;
        debugln!("topic {} seq {}", topic, seq);
        debugln!("request: seq {} received, topic is {}", seq, topic);
        let root = rmpv::decode::read_value(&mut &payload[..]).map_err(map_rmpv_read_err)?;
        debugln!("topic {} resp {:?}", topic, root);
        if !root.is_map() {
            return Err(Error::Other(format!("topic {} response invalid", topic)));
        }
        let mut resp = Response {
            seq,
            topic: String::from(topic),
            payload: Vec::from(payload),
            rc: 500,
            rd: String::from("read response timeout"),
            body: None,
        };
        let mut is_ok = false;
        for (key, val) in root.as_map().unwrap() {
            if !key.is_str() {
                continue;
            }
            match key.as_str().unwrap() {
                "rc" => {
                    if !val.is_i64() {
                        errorln!("invalid rc value {:?}", val);
                    }
                    resp.rc = val.as_i64().unwrap() as i32;
                    is_ok = true;
                }
                "rd" => {
                    if !val.is_str() {
                        errorln!("invalid rd value {:?}", val);
                    }
                    resp.rd = String::from(val.as_str().unwrap());
                }
                "data" => {
                    if val.is_map() {
                        resp.body = Some(val.clone());
                    }
                }
                other => {
                    warnln!("unknown key {}", other);
                }
            }
        }
        if !is_ok {
            return Err(Error::Other(format!("topic {} seq {} invalid", topic, seq)));
        }
        self.sender.send(Message::Response(seq, resp));
        Ok(())
    }

    fn check_if_match_my_info(&self, topic: &str, top_items: &Vec<&str>) -> Result<()> {
        let index = if top_items.len() == 8 {
            // update props: v1/dev/%s/%s/model/props/r/%d
            // call method : v1/dev/%s/%s/model/method/r/%d
            2
        } else if top_items.len() == 9 {
            //call sys request: v1/f/product_code/device_uid/t/dst_domain/dst_id/a/%d
            2
        } else if top_items.len() == 11 {
            //update props: v1/f/src_domain/src_service_id/t/%s/%s/model/props/r/%d
            //call method : v1/f/src_domain/src_service_id/t/%s/%s/model/method/r/%d
            5
        } else {
            return Err(Error::Other(format!("topic request {} unknown", topic)));
        };
        let param = PersisParams::get();
        if top_items[index] != from_slice_with_null_to_str(&param.product_code)
            && top_items[index + 1] != from_slice_with_null_to_str(&param.device_uid)
        {
            return Err(Error::Other(format!(
                "topic {} not match my product code and device uid",
                topic
            )));
        }
        Ok(())
    }
}

pub struct Response {
    pub seq: u16,
    pub topic: String,
    pub payload: Vec<u8>,
    pub rc: i32,
    pub rd: String,
    pub body: Option<Value>,
}
